#include <queue>
#include <string>

//#include <boost/array.hpp>
//#include <boost/bind.hpp>
#include <boost/smart_ptr.hpp>
//#include <boost/random.hpp>
//#include <boost/asio.hpp>

//#include "TcpTunnelBuilder.h"
//#include "XORCryptoFactory.h"
//#include "BandwithLimitSendProxy.h"
#include "StartFacade.h"

#include <iostream>

using namespace std;
//using namespace boost::asio;
using namespace encrypted::tunnel;
/*
// Convert from char to ascii char for hex
// Ex: input from = 0x6d, output hi = '6' and lo = 'd'
void tohex(unsigned char& hi, unsigned char& lo, unsigned char from)
{
    hi = from >> 4;
    if(hi > 9)
        hi += 'a' - 10;
    else
        hi += '0';
    lo = from & 0x0f;
    if(lo > 9)
        lo += 'a' - 10;
    else
        lo += '0';
}

// Convert hi and low from hex to char
// Ex: input hi = 'c' and lo = '3', return is 0xc3
unsigned char fromhex(unsigned char hi, unsigned char lo)
{
    if(hi >= 'a')
        hi -= 'a' - 10;
    else
        hi -= '0';
    if(lo >= 'a')
        lo -= 'a' - 10;
    else
        lo -= '0';
    return hi << 4 | lo;
}
*/
void printHelp()
{
    cout << "Usage: ./encryptedtunnel <options>" << endl;
    cout << "Options:" << endl;
    cout << "  -c               Run in the client mode (default)." << endl;
    cout << "  -s               Run in the server mode." << endl;
    cout << "  -l <port>        Specify the port to listen. Required for both modes." << endl;
    cout << "  -i <ip>          Specify the IP address to connect. Required for both modes." << endl;
    cout << "  -p <port>        Specify the port to connect. Required for both modes." << endl;
    cout << "  -w <limit>       Specify the bandwidth limit (Byte per second, default is 102400)." << endl;
    cout << "  -b <host name>   Specify the host name to bind." << endl;
    cout << "  -k <secret key>  Specify the secret key used. Required for client mode." << endl;
}
/*
//cout << "The callback function when secret key has been generated in server mode.
//Can modify this function to get the generated secret key when using GUI, such as QT.  secret key is "
//<<secretKey << endl;
void secretKeyProcess(string secretKey)
{
}

int start(string listenPort,
          string connectPort,
          string connectIP,
          int mode = 1,
          unsigned int bandWidth = 102400,
          unsigned int randSeed = 0,
          string secretKey = "",
          string bindHost = "")
{
    typedef XORCryptoFactory<boost::shared_array<unsigned char> > CryptoFactory;
    typedef boost::shared_ptr<CryptoFactory> CryptoFactoryPtr;
    typedef CryptoFactory::DecryptorPtr CryptoPtr;
    typedef BandwithLimitSendProxy<CryptoPtr> SendProxy;
    typedef boost::shared_ptr<SendProxy> SendProxyPtr;

    const size_t keysize = 32;

    // Construct a secret key buffer
    boost::shared_array<unsigned char> key(new unsigned char[keysize]);

    // If mode == 1 means it's server, then generate the secret key
    if( mode == 1 )
    {
        // The randSeed should be generated by the random number generator of system.
        // Just use the current time as the seed is not very safe.

        if(randSeed == 0)
            cerr << "WARN:Random Seed equals zero is not safe when running mode is server.  Please pass the real random seed to the start function." << endl;
        boost::mt19937 rnd;
        rnd.seed(randSeed);
        boost::variate_generator<boost::mt19937&,
                                 boost::uniform_smallint<> > randomizer(rnd, boost::uniform_smallint<>(0, 0x0ff));
        cout << "INFO:Generated Secret Key is:" << endl;
        cout << "INFO:";
        unsigned char hi, lo;
        secretKey = "";
        for(size_t i = 0; i < keysize; ++i)
        {
            key[i] = randomizer();
            tohex(hi, lo, key[i]);
            cout << hi << lo;
            secretKey += hi;
            secretKey += lo;
        }
        cout << endl;
        cout << "INFO:Please send the secret key above to your users." << endl;
        secretKeyProcess(secretKey);
    }

    // If mode != 1 means it's client, then use the secret_key as the secret key
    else
    {
        if ( secretKey == "" )
        {
            cerr<< "FATAL:No Serect Key" << endl;
            exit(1);
        };
        for(size_t i = 0; secretKey[i] != '\0' && secretKey[i + 1] != '\0'; i += 2)
            key[i / 2] = fromhex(secretKey[i], secretKey[i + 1]);
    }
    if(bindHost == "")
        bindHost = ip::host_name();
    try
    {
        io_service io_service;
        SendProxyPtr sendProxy(new SendProxy(io_service, bandWidth));
        sendProxy->start();
        for(ip::tcp::resolver::iterator it = ip::tcp::resolver(io_service).resolve(ip::tcp::resolver_query(bindHost, listenPort)), end; it != end; ++it)
            (new TcpTunnelBuilder<CryptoFactoryPtr, SendProxyPtr>(io_service, it->endpoint(), connectIP, connectPort, CryptoFactoryPtr(new CryptoFactory(key, keysize)), sendProxy))->start();
        io_service.run();
    }
    catch (const exception& e)
    {
        cerr << "FATAL:" << e.what() << endl;
        return -1;
    }
    return 0;
}
*/
int main(int argc, char* argv[])
{
    //mode equals 0 means client mode and mode equals 1 means server mode.
    StartFacade::StartMode mode = StartFacade::ClientMode;
    int bandWidth = 102400;
    string listenPort, connectPort, connectIP, secrectKey, bindHost;

    for ( int i = 1; i < argc; ++i)
    {
        if ( argv[i][0] != '-' )
        {
            cerr << "FATAL:Options Error!" << endl;
            printHelp();
            exit(1);
        };
        switch ( argv[i][1] )
        {
        case 'c': // client mode
            mode = StartFacade::ClientMode;
            break;
        case 's': // server mode
            mode = StartFacade::ServerMode;
            break;
        case 'l':
            listenPort = argv[++i];
            break;
        case 'p':
            connectPort = argv[++i];
            break;
        case 'i':
            connectIP = argv[++i];
            break;
        case 'w':
            bandWidth = atoi(argv[++i]);
            break;
        case 'k':
            secrectKey = argv[++i];
            break;
        case 'b':
            bindHost = argv[++i];
            break;
        case 'h':
            printHelp();
            return 0;
        default:
            cerr << "FATAL:Unrecognized Option(s)" << endl; exit(1);
            break;
        }
        if ( i == argc )
        {
            cerr << "FATAL:Wrong Option(s)" << endl;
            printHelp();
            exit(1);
        };
    }

    if ( listenPort == "" || connectPort == "" || connectIP == "")
    {
        cerr << "FATAL:Options are not enough" << endl;
        printHelp();
        exit(1);
    };

    class PrintSecretKeyGeneratedListener : public SecretKeyGeneratedListener
    {
        virtual void secretKeyGenerated(std::string secretKey)
        {
            cout << "INFO:" << secretKey << endl;
        }
    };

    boost::shared_ptr<SecretKeyGeneratedListener> printKeyListener = boost::shared_ptr<SecretKeyGeneratedListener>((SecretKeyGeneratedListener*)new PrintSecretKeyGeneratedListener());
    StartFacade startor;
    startor.addSecretKeyGeneratedListener(printKeyListener);
    int error = startor.addService(listenPort, connectPort, connectIP, mode, secrectKey, bindHost, 0, bandWidth);
    if(error != 0)
        return error;
    return startor.start();
}
